class Watch {
	constructor(context) {
		this.page = context
		this.observers = new Map()
		this.init()
	}

	// 将 watch 数据写入观察者队列
	init() {
		const ctx = this.page

		Object.keys(ctx.watch).forEach(path => {
			if (this.inData(path)) {
				this.observers.set(path, ctx.watch[path])
			}
		})
	}

	// 更新值并触发回调 不更新视图
	data(path, newVal) {
		if (this.inData(path)) {
			this.setter(this.page.data, path, newVal)
		}
	}

	// 更新值并触发回调 并更新视图
	setData(obj) {
	 	const oldData = JSON.parse(JSON.stringify(this.page.data));
		this.page.setData(obj)
		Object.keys(obj).forEach(path => {
			if (this.observers.has(path)) {
				this.notify(path, obj[path], this.getter(oldData, path))
			}
		})
	}

	// 触发key的对应回调
	notify(key, newVal, oldVal) {
		if (!this.observers.has(key)) return
		this.observers.get(key).call(this.page, newVal, oldVal)
	}

	// 删除观察者
	unSubscribe(key) {
		this.observers.delete(key)
	}

	/**
	 * 根据路径设置 data 值
	 * @param {string} path
	 * @param {any} newVal
	 */
	setter(data, path, newVal) {
		if (!this.inData(path)) return

		let oldVal
		const pathArr = this._getPathArr(path)
		let res = pathArr.reduce((res, currentPath, currentIndex) => {
			// assign the new value to the key
			if (currentIndex === pathArr.length - 1) {
				oldVal = res[currentPath]
				res[currentPath] = newVal
			}

			return res[currentPath]
		}, data)

		if (this.observers.has(path)) {
			this.notify(path, newVal, oldVal)
		}

		return res
	}

	/**
	 * getter 根据路径获取对应 key 的 value
	 *
	 * @param {object} data data 对象
	 * @param {string} path data 对象下的某一键值对的索引
	 * @return {any} keyValue
	 *
	 * e.g.
	 * var data = {"a":{"b":{"c":{"d":123}}}}
	 * getter(data, 'a.b[c]') // return {"d":123}
	 */
	getter(data, path) {
		const pathArr = this._getPathArr(path)
		return pathArr.reduce((res, currentPath) => {
			const currentValueType = Object.prototype.toString.call(res)
			return /String|Number|Boolean|Null|Undefined/.test(currentValueType)
				? undefined
				: res[currentPath]
		}, data)
	}

	// check if data.path exist
	inData(path) {
		return this.getter(this.page.data, path) !== undefined
	}

	_getPathArr(path) {
		const REG_KEY = /\[((?:\S+?))\]|\./g
		return path.toString().split(REG_KEY).filter(item => !!item)
	}
}

export default Watch